package com.vinoxm.rss.subscribe;

import com.vinoxm.common.base.exception.BusinessException;
import com.vinoxm.common.base.result.HandleResult;
import com.vinoxm.common.utils.CollectionUtils;
import com.vinoxm.common.utils.StringUtils;
import com.vinoxm.rss.model.RssResult;
import com.vinoxm.rss.model.RssSubscribe;
import com.vinoxm.rss.subscribe.config.SubscribeMatcher;
import com.vinoxm.rss.subscribe.config.SubscribeMatcherConfig;
import com.vinoxm.rss.subscribe.handler.AbstractSubscribeHandler;
import com.vinoxm.rss.subscribe.handler.DefaultSubscribeHandler;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;
import org.apache.tomcat.util.threads.ThreadPoolExecutor;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Component
@Log4j2
public class SubscribeAdapter implements InitializingBean {

    @Setter(onMethod_ = @Resource)
    private SubscribeMatcherConfig subscribeMatcherConfig;

    private final List<AbstractSubscribeHandler> handlerMapping = new ArrayList<>();

    private final List<SubscribeMatcher> subscribeMatchers = new ArrayList<>();

    private final ThreadLocal<List<RssResult>> multipleResult = new ThreadLocal<>();

    private final ThreadPoolExecutor executor = new ThreadPoolExecutor(6, 6, 0, TimeUnit.SECONDS, new LinkedBlockingDeque<>(500), new ThreadPoolExecutor.CallerRunsPolicy());

    private void putResultList(List<RssResult> resultList) {
        List<RssResult> results = multipleResult.get();
        if (null != results && results.size() > 0) {
            results.addAll(resultList);
            multipleResult.set(results);
        } else {
            multipleResult.set(resultList);
        }
    }

    public List<RssResult> getResultList() {
        return multipleResult.get();
    }

    public List<RssResult> getResultListAndRemove() {
        List<RssResult> results = multipleResult.get();
        multipleResult.remove();
        return results;
    }

    private AbstractSubscribeHandler getHandlerByUrl(String url) {
        try {
            URL url1 = new URL(url);
            return CollectionUtils.find(handlerMapping, handler -> handler.containsUrl(url1));
        } catch (MalformedURLException e) {
            log.info("No matching handler!");
            return null;
        }
    }

    public List<RssResult> handleRssSubscribe(RssSubscribe rss, boolean putIntoMap) {
        String url = rss.getUrl();
        StringUtils.ifEmptyThrow(url, new BusinessException("url is empty!"));
        AbstractSubscribeHandler handler = getHandlerByUrl(url);
        List<RssResult> results = null != handler ? handler.doHandler(rss) : null;
        if (null != handler && putIntoMap) putResultList(results);
        return results;
    }

    public HandleResult<RssResult> handleRssSubscribe(List<RssSubscribe> rssSubsList) {
        List<RssSubscribe> collect = rssSubsList.stream().filter(r -> !StringUtils.isEmpty(r.getUrl())).collect(Collectors.toList());
        CopyOnWriteArrayList<HandleResult.FailedJson> failedList = new CopyOnWriteArrayList<>();
        CopyOnWriteArrayList<RssResult> resultList = new CopyOnWriteArrayList<>();
        CompletableFuture.allOf(collect.stream().map(rss -> CompletableFuture.runAsync(() -> {
            AbstractSubscribeHandler handler = getHandlerByUrl(rss.getUrl());
            if (null != handler) {
                List<RssResult> results = handler.doHandler(rss);
                if (results == null) failedList.add(HandleResult.FailedJson.fetchUrlError(rss.getName()));
                if (null != results && results.size() > 0) resultList.addAll(results);
            } else {
                failedList.add(HandleResult.FailedJson.noSuchHandler(rss.getName()));
            }
        }, executor)).collect(Collectors.toList()).toArray(new CompletableFuture[]{})).join();
        putResultList(resultList);
        return new HandleResult<>(collect.size(), resultList, failedList);
    }

    public List<SubscribeMatcher> getSubscribeMatchers() {
        return this.subscribeMatchers;
    }

    @Override
    public void afterPropertiesSet() {
        List<SubscribeMatcher> matchers = subscribeMatcherConfig.getMatchers();
        if (null == matchers) return;
        List<SubscribeMatcher> collect = matchers.stream().filter(this::checkMatchProperties).collect(Collectors.toList());
        if (collect.size() == 0) return;
        collect.forEach(matcher -> {
            subscribeMatchers.add(matcher);
            DefaultSubscribeHandler handler = new DefaultSubscribeHandler(matcher.getRegex());
            handlerMapping.add(handler);
            log.info(String.format("[Subscribe Adapter] add matcher: %s", matcher.getName()));
        });
    }

    private boolean checkMatchProperties(SubscribeMatcher matcher) {
        return !StringUtils.isEmpty(matcher.getName()) && !StringUtils.isEmpty(matcher.getSource()) &&
                !StringUtils.isEmpty(matcher.getRegex()) && !StringUtils.isEmpty(matcher.getMatch()) &&
                (!matcher.getKeyword() || matcher.getMatch().indexOf("#{keyword}") > 0);
    }
}
